/*
Authors: Eli Yucht 300388949 and Lior Fass 300069705
Project: Hamming
Description: receiver class implementation
*/


#include "receiver.h"

// Initialize receiver object fields
Receiver::Receiver()
{
	file_p = NULL;
	rcvr_socket = INVALID_SOCKET;
	nRecivedBytes = 0;
	nReconstructedBytes = 0;
	nErrors = 0;
}
// Free receiver object's fields (when finish)
Receiver::~Receiver()
{
	if (file_p != NULL)
		fclose(file_p);
	closesocket(rcvr_socket);
	WSACleanup();
};


/* Initialize the receiver:
	1. check the arguments for receiver
	2. open the output file
	3. try to connect to the channel

*/
int Receiver::Init(int argc, char *argv[])
{
	int status = Check_Arguments(argc,argv);
	if (status)
	{
		cout << "args Check Failed" << endl;
		return 1;
	}

	file_p = fopen(argv[3], "wb");
	if (file_p == NULL)
	{
		cerr << "Output File open failed\n";
		return 1;
	}

	rcvr_socket = Socket_tool::Connect_Channel(argv[1], (unsigned short)atoi(argv[2]));
	if (rcvr_socket == INVALID_SOCKET)
	{
		cerr << "Socket opening failed" << endl;
		return 1;
	}

	return 0; // Great success obj initialized
}

/* check the receiver's arguments:
	1. check vaild argc
	2. check valid ip address
	3. check valid port number

*/


int Receiver::Check_Arguments(int argc, char *argv[])
{
	if (argc != NUM_OF_ARGS + 1)
	{
		cerr << "Invalid number of arguments for receiver\n";
		return 1;
	}
	unsigned long ip_addr = inet_addr(argv[1]);

	if (ip_addr == INADDR_NONE)
	{
		cerr << "Invalid ip address\n";
		return 1;
	}

	if ((atoi(argv[2]) < 0) | (atoi(argv[2]) > 65536))
	{
		cerr << "Invalid port number (should be a number between 0-65536)\n";
		return 1;
	}

	return 0; // Arg Passed
}

/*  Run the receiver:
		1. Read bytes from the connected TCP socket
		2. Decode the bytes (correct error if occurred) and write it to the output file
		3. Print and send information back to the sender (via the channel)
		4. Close the socket

*/

int Receiver::Run()
{
	int status = 0;

	status = Receive_Decode_WriteFile();
	if (status)
	{
		cout << "receiver failed to decode/write file" << endl;
		return 1;
	}

	status = PrintAndSendResult();
	if (status)
	{
		cout << "send result to sender failed" << endl;
		return 1;
	}

	status = shutdown(rcvr_socket, SD_SEND);
	if (status == SOCKET_ERROR)
	{
		cerr << "shutdown for receiver socket failed: " << WSAGetLastError() << "\n";
		return 1;
	}

	return 0;
}

/*  Received bytes from the connected socket decode (and correct error if occurred) them
	and write the result to the output file

	1. Read bytes from the connected TCP socket (until the socket disconnected by the sender)
	2. Decode each 31 bit block to 26 bit block - (hamming decoding) and correct error if occurred
	3. Rearrange the corrected 26 bit blocks to bytes and write them in the file

*/
int Receiver::Receive_Decode_WriteFile()
{
	int bytesRemaining;
	size_t countW2F = 0, elements_read = 0;
	TransferResult_t socketResult = TransferResult_t::TRNS_FAILED;
	unsigned char bytes_rcvd[31];
	unsigned char res_hamming_blocks[32];
	unsigned char res_buffer_bytes[26];

	while (socketResult != TransferResult_t::TRNS_DISCONNECTED)
	{
		socketResult = Socket_tool::ReceiveBuffer((char*)bytes_rcvd, 31, &bytesRemaining, rcvr_socket);
		nRecivedBytes += 31 - bytesRemaining;
		if (socketResult == TransferResult_t::TRNS_FAILED)
		{
			cerr << "Socket Failed" << endl;
			return 1;
		}
		else if (socketResult == TransferResult_t::TRNS_DISCONNECTED)
		{
			if (bytesRemaining == 15) // we have reached the last 16 bytes in the socket => the last 13 bytes in the file
			{
				Bytes_Man::Bytes_to_Block31(res_hamming_blocks, bytes_rcvd, 4);
				nErrors += Hamming_EDM::decode_Blocks(res_hamming_blocks, 4);
				Bytes_Man::bits_26_to_Byte(res_hamming_blocks, res_buffer_bytes);
				countW2F = fwrite(res_buffer_bytes, 1, 13, file_p);
				if (countW2F != 13)
				{
					cerr << "File write failed at EOF\n";
					return 1;
				}
				else
					nReconstructedBytes += countW2F;
			}
			else if (bytesRemaining != 31)
			{
				cerr << "Socket disconnected unexpectedly" << endl;
				return 1;
			}
		}
		else if (socketResult == TransferResult_t::TRNS_SUCCEEDED) // guarantee 31 Bytes
		{
			Bytes_Man::Bytes_to_Block31(res_hamming_blocks, bytes_rcvd, 8);
			nErrors += Hamming_EDM::decode_Blocks(res_hamming_blocks, 8);
			Bytes_Man::bits_26_to_Byte(res_hamming_blocks, res_buffer_bytes);
			Bytes_Man::bits_26_to_Byte(res_hamming_blocks + 16, res_buffer_bytes + 13);
			countW2F = fwrite(res_buffer_bytes, 1, 26, file_p);
			if (countW2F != 26)
			{
				cerr << "File write failed\n";
				return 1;
			}
			else
				nReconstructedBytes += countW2F;
		}
	}
	return 0;
}

//  Print and send information back to the sender (via the channel)

int Receiver::PrintAndSendResult()
{
	TransferResult_t status;
	char str[128]; //more then enough to represent 3 ints
	sprintf(str, "recieved:       %d bytes\nreconstructed:  %d bytes\ncorrected:      %d errors\n", nRecivedBytes, nReconstructedBytes, nErrors);
	cerr << str;
	status = Socket_tool::SendString(str, rcvr_socket);
	if (status != TransferResult_t::TRNS_SUCCEEDED)
	{
		cout << "transmission of result string failed " << WSAGetLastError() << endl;
		return 1;
	}
	return 0;
}